#include <cad-slicer/slice.h>

#include "output.h"


namespace cadslicer
{


void slice::finish_edges(void)
{
  check_neighbours();
  check_intersections();

  if (_intersections.size())
  {
    std::set<triangle_intersection const*> visited;

    _contours.reserve(_intersections.size()/3);
    _boxes.reserve(_intersections.size()/3);

    for (auto const& to_visit : _intersections)
      if (!visited.count(&to_visit.second))
      {
        triangle_intersection const* current_triangle = &to_visit.second;
        edge_id first_visited_edge = current_triangle->first;
        auto itEnd = _neighbours.end();

        edge_id last_edge = current_triangle->first;
        edge_id new_edge = current_triangle->second;

        contour current_contour;
        current_contour.reserve(_intersections.size());

        current_contour.push_back(current_triangle->intersection.first);

        // Init bounding box values.
        float x_min = current_triangle->intersection.first.real();
        float x_max = x_min;
        float y_min = current_triangle->intersection.first.imag();
        float y_max = y_min;

        while (new_edge != first_visited_edge)
        {
          // Update.
          auto it = _neighbours.find(new_edge);

//          if (it == itEnd) break;
          prenstar_assert_false(it == itEnd);

          triangle_neighbour const* neigh = &it->second;

          prenstar_assert_true(
                neigh->first == current_triangle ||
                neigh->second == current_triangle);

          prenstar_assert_true(neigh->first && neigh->second);

          triangle_intersection const* new_triangle =
              (neigh->first == current_triangle)
              ? neigh->second
              : neigh->first;

          visited.insert(new_triangle);

          prenstar_assert_true(
                new_triangle->first == new_edge ||
                new_triangle->second == new_edge);

          last_edge = new_edge;

          if (last_edge == new_triangle->first)
          {
            new_edge = new_triangle->second;
            current_contour.push_back(new_triangle->intersection.second);

            x_min = std::min(x_min, new_triangle->intersection.second.real());
            x_max = std::max(x_max, new_triangle->intersection.second.real());
            y_min = std::min(y_min, new_triangle->intersection.second.imag());
            y_max = std::max(y_max, new_triangle->intersection.second.imag());
          }
          else
          {
            new_edge = new_triangle->first;
            current_contour.push_back(new_triangle->intersection.first);
            x_min = std::min(x_min, new_triangle->intersection.first.real());
            x_max = std::max(x_max, new_triangle->intersection.first.real());
            y_min = std::min(y_min, new_triangle->intersection.first.imag());
            y_max = std::max(y_max, new_triangle->intersection.first.imag());
          }

          current_triangle = new_triangle;
        }

        current_contour.shrink_to_fit();
        _contours.emplace_back(std::move(current_contour));
        _boxes.emplace_back(bounding_box{x_min,x_max,y_min,y_max});
      }
  }

  prenstar_assert_eq(_contours.size(), _boxes.size());

  _contours.shrink_to_fit();
  _boxes.shrink_to_fit();

  _intersections.clear();
  _neighbours.clear();

  for (unsigned i = 0; i < _contours.size(); ++i)
    if (!insert_in_hierarchy(&_hierachy, i))
      throw std::runtime_error("Invalid object: contour crossing.");
}


void slice::check_neighbours(void)
{
  std::set<triangle_id const*> invalid;

  for (std::pair<edge_id, triangle_neighbour> const& neigh : _neighbours)
  {
    prenstar_assert_false(!neigh.second.first && !neigh.second.second);

    /**/ if (!neigh.second.first)
      invalid.insert(&neigh.second.second->vertice);
    else if (!neigh.second.second)
      invalid.insert(&neigh.second.first->vertice);
  }

  if (!invalid.empty())
  {
    std::stringstream ss;

    ss << "At slice (z = " << get_z() << "):\n";

    for (triangle_id const* triangle : invalid)
    {
      ss << "Invalid triangle at: "
         << (*triangle) << "\n"
            "  -> no neighbour.\n";
    }

    ss << "List of triangles:\n";

    for (std::pair<triangle_id, triangle_intersection> const& triangle : _intersections)
    {
      ss << triangle.first << "\n" << std::hex << "  -> "
         << triangle.second << "\n";
    }

    ss << "List of neighbours:\n";

    for (std::pair<edge_id, triangle_neighbour> const& neigh : _neighbours)
    {
      ss << std::hex << neigh.first << "\n  for ";
      if (neigh.second.first)
        ss << neigh.second.first->vertice;
      else ss << "nullptr";

      ss << "\n  and ";

      if (neigh.second.second)
        ss << neigh.second.second->vertice;
      else ss << "nullptr";

      ss << "\n";
    }

    throw std::runtime_error(ss.str());
  }
}


void slice::check_intersections(void)
{

}


bool slice::insert_in_hierarchy(slice::hierarchy* hier, unsigned idx)
{
  prenstar_assert_not_nullptr(hier);
  prenstar_assert_lt(idx, _contours.size());
  prenstar_assert_eq(_contours.size(), _boxes.size());

  for (hierarchy* child : hier->get_children())
  {
    // If box of _contour[i] included in box of child
    // then insert in child.
    /**/ if (_boxes[*child][0] <= _boxes[idx][0] &&
             _boxes[*child][1] >= _boxes[idx][1] &&
             _boxes[*child][2] <= _boxes[idx][2] &&
             _boxes[*child][3] >= _boxes[idx][3])
    {
      insert_in_hierarchy(child, idx);
      return true;
    }
    // Else check no crossing, otherwise invalid object.
//    else if (_boxes[idx][0] >= _boxes[*child][1] ||
//             _boxes[idx][1] <= _boxes[*child][0] ||
//             _boxes[idx][2] >= _boxes[*child][3] ||
//             _boxes[idx][3] <= _boxes[*child][2])
//    {
//      return false;
//    }
  }

  // If no child contain _contour[_idx] then this is a new child.
  hier->_children.emplace_front(new hierarchy(*hier, idx));

  return true;
}


} // cadslicer


